﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FeatureModelCore.FMShapes;
using BoardAlgorithm;
using FeatureModelCore;
using FeatureModelCore.Layout;

namespace FeatureModelTool.FMAlgorithms
{
    public class ConstraintGraphicalAlgorithm
    {
        public static readonly double ARROW_LENGTH = TreeLayout.BLOCK_SIZE;


        /// <summary>
        /// Return the turn point of the line
        /// </summary>
        /// <param name="startPointLeft"></param>
        /// <param name="endPoint"></param>
        /// <param name="rectList"></param>
        /// <param name="circleList"></param>
        /// <param name="canvasWidth"></param>
        /// <param name="canvasHeight"></param>
        /// <param name="blockSize"></param>
        /// <returns></returns>
        public static FMPoint[] GetLinePath(FMRectangle startRect, FMRectangle endRect, FMRectangle[] rectList
            , FMCircle[] circleList, double canvasWidth, double canvasHeight, double blockSize)
        {
            double tmpWidth, tmpHeight;
            ResetCanvasSize(out tmpWidth, out tmpHeight, rectList.ToArray<FMRectangle>()
                , circleList.ToArray<FMCircle>());

            if (canvasWidth < tmpWidth)
            {
                canvasWidth = tmpWidth;
            }
            if (canvasHeight < tmpHeight)
            {
                canvasHeight = tmpHeight;
            }

            int width = convertDouble2Int(canvasWidth / blockSize) + 1;
            int height = convertDouble2Int(canvasHeight / blockSize) + 1;

            FMPoint startPointLeft = new FMPoint(startRect.Center.X - startRect.Width / 2 - ARROW_LENGTH
                , startRect.Center.Y);
            FMPoint startPointRight = new FMPoint(startRect.Center.X + startRect.Width / 2 + ARROW_LENGTH
                , startRect.Center.Y);

            FMPoint endPointLeft = new FMPoint(endRect.Center.X - endRect.Width / 2 - ARROW_LENGTH
    , endRect.Center.Y);
            FMPoint endPointRight = new FMPoint(endRect.Center.X + endRect.Width / 2 + ARROW_LENGTH
                , endRect.Center.Y);

            BoardPoint startPosLeft = new BoardPoint(convertDouble2Int(startPointLeft.X / blockSize)
                , convertDouble2Int(startPointLeft.Y / blockSize));
            BoardPoint startPosRight = new BoardPoint(convertDouble2Int(startPointRight.X / blockSize)
                , convertDouble2Int(startPointRight.Y / blockSize));
            BoardPoint endPosLeft = new BoardPoint(convertDouble2Int(endPointLeft.X / blockSize)
                , convertDouble2Int(endPointLeft.Y / blockSize));
            BoardPoint endPosRight = new BoardPoint(convertDouble2Int(endPointRight.X / blockSize)
                , convertDouble2Int(endPointRight.Y / blockSize));

            List<BoardPoint> blockList = new List<BoardPoint>();
            // insert rect block
            foreach (FMRectangle rect in rectList)
            {
                int top = convertDouble2Int((rect.Center.Y - rect.Height / 2) / blockSize);
                int bottom = convertDouble2Int((rect.Center.Y + rect.Height / 2) / blockSize);
                int left = convertDouble2Int((rect.Center.X - rect.Width / 2) / blockSize);
                int right = convertDouble2Int((rect.Center.X + rect.Width / 2) / blockSize);

                for (int i = top; i <= bottom; ++i)
                    for (int j = left; j <= right; ++j)
                    {
                        blockList.Add(new BoardPoint(j, i));
                    }
            }
            // insert circl block
            foreach (FMCircle cir in circleList)
            {
                int top = convertDouble2Int((cir.Center.Y - cir.Radius) / blockSize);
                int bottom = convertDouble2Int((cir.Center.Y + cir.Radius) / blockSize);
                int left = convertDouble2Int((cir.Center.X - cir.Radius) / blockSize);
                int right = convertDouble2Int((cir.Center.X + cir.Radius) / blockSize);

                for (int i = top; i <= bottom; ++i)
                    for (int j = left; j <= right; ++j)
                    {
                        blockList.Add(new BoardPoint(j, i));
                    }
            }

            // generate the board
            Board board = new Board(height, width, startPosLeft, startPosRight, endPosLeft, endPosRight
                , blockList.ToArray<BoardPoint>());

            BoardPoint[] pathList = board.GetPath();

            return getLinePoints(pathList, startPointLeft, startPointRight
                , endPointLeft, endPointRight, blockSize);
        }

        private static int convertDouble2Int(double d)
        {
            return (int)(d + 0.5);
        }

        public static void ResetCanvasSize(out double width, out double height
            , FMRectangle[] rectList, FMCircle[] cirList)
        {
            width = 0;
            height = 0;

            foreach (FMRectangle rect in rectList)
            {
                double tmpWidth = rect.Center.X + rect.Width / 2 + TreeLayout.MARGIN;
                double tmpHeight = rect.Center.Y + rect.Height / 2 + TreeLayout.MARGIN;

                if (width < tmpWidth)
                {
                    width = tmpWidth;
                    height = tmpHeight;
                }
            }

            foreach (FMCircle circle in cirList)
            {
                double tmpWidth = circle.Center.X + circle.Radius + TreeLayout.MARGIN;
                double tmpHeight = circle.Center.Y + circle.Radius + TreeLayout.MARGIN;

                if (width < tmpWidth)
                {
                    width = tmpWidth;
                    height = tmpHeight;
                }
            }
        }

        public static FMPoint[] getLinePoints(BoardPoint[] pathList
            , FMPoint startPointLeft
            , FMPoint startPointRight
            , FMPoint endPointLeft
            , FMPoint endPointRight, double blockSize)
        {

            FMPoint startPos = null, endPos = null;
            FMPoint startEdge = null, endEdge = null;
            #region  check which the start and the end
            BoardPoint startPosLeft = new BoardPoint(convertDouble2Int(startPointLeft.X / blockSize)
    , convertDouble2Int(startPointLeft.Y / blockSize));
            BoardPoint startPosRight = new BoardPoint(convertDouble2Int(startPointRight.X / blockSize)
                , convertDouble2Int(startPointRight.Y / blockSize));
            BoardPoint endPosLeft = new BoardPoint(convertDouble2Int(endPointLeft.X / blockSize)
                , convertDouble2Int(endPointLeft.Y / blockSize));
            BoardPoint endPosRight = new BoardPoint(convertDouble2Int(endPointRight.X / blockSize)
                , convertDouble2Int(endPointRight.Y / blockSize));

            if (pathList[0].Equals(startPosLeft))
            {
                startPos = startPointLeft;
                startEdge = new FMPoint(startPos.X + ARROW_LENGTH, startPos.Y);
            }
            else if (pathList[0].Equals(startPosRight))
            {
                startPos = startPointRight;
                startEdge = new FMPoint(startPos.X - ARROW_LENGTH, startPos.Y);
            }
            else
            {
                throw new Exception("");
            }

            if (pathList[pathList.Length - 1].Equals(endPosLeft))
            {
                endPos = endPointLeft;
                endEdge = new FMPoint(endPos.X + ARROW_LENGTH, endPos.Y);
            }
            else if (pathList[pathList.Length - 1].Equals(endPosRight))
            {
                endPos = endPointRight;
                endEdge = new FMPoint(endPos.X - ARROW_LENGTH, endPos.Y);
            }
            else
            {
                throw new Exception("");
            }
            #endregion

            List<FMPoint> pointList = new List<FMPoint>();
            if (pathList.Length == 2)
            {
                BoardPoint p1 = pathList[0];
                BoardPoint p2 = pathList[1];

                pointList.Add(startEdge);
                pointList.Add(startPos);
                
                if (p1.X == p2.X)
                {
                    endPos = new FMPoint(startPos.X, endPos.Y);
                }
                else if (p1.Y == p2.Y)
                {
                    endPos = new FMPoint(endPos.X, startPos.Y);
                }

                pointList.Add(endPos);
                pointList.Add(endEdge);

                return pointList.ToArray<FMPoint>();
            }
            pointList.Add(startEdge);
            foreach (BoardPoint bp in pathList)
            {
                pointList.Add(new FMPoint(bp.X * blockSize, bp.Y * blockSize));
            }
            pointList.Add(endEdge);

            BoardPoint _p1 = pathList[0];
            BoardPoint _p2 = pathList[1];

            // start
            pointList[1] = startPos;
            if (_p1.X == _p2.X)
            {
                pointList[2] = new FMPoint(startPos.X, pointList[2].Y);
            }
            else if (_p1.Y == _p2.Y)
            {
                pointList[2] = new FMPoint(pointList[2].X, startPos.Y);
            }

            _p1 = pathList[pathList.Length - 1];
            _p2 = pathList[pathList.Length - 2];
            // end
            pointList[pointList.Count - 2] = endPos;
            if (_p1.X == _p2.X)
            {
                pointList[pointList.Count - 3] = new FMPoint(endPos.X, pointList[pointList.Count - 3].Y);
            }
            else if (_p1.Y == _p2.Y)
            {
                pointList[pointList.Count - 3] = new FMPoint(pointList[pointList.Count - 3].X, endPos.Y);
            }

            return pointList.ToArray<FMPoint>();
        }       
    }
}
